package top.hudk.multithreading.sycn;

import java.net.Socket;
import java.util.concurrent.locks.ReentrantLock;
import org.openjdk.jol.info.ClassLayout;

/**
 * 类描述：演示 synchronized 锁状态的各种变化过程
 *
 * 依赖以下功能：打印对象的内存布局
 *     <dependency>
 *         <groupId>org.openjdk.jol</groupId>
 *         <artifactId>jol-core</artifactId>
 *         <version>0.3</version>
 *     </dependency>
 *
 * @author HuDongkang
 * @date 2021/11/3
 */
public class LockStudy {



    /**
     * 未启用偏向锁模式
     * 演示过程：未锁定 -> 轻量级锁 -> 重量级锁
     */
    public static void methodA(){
        Object monitor = newObject();
        print(monitor,"加锁前-未锁定状态(001)");
        //演示两个线程竞争同一把锁
        //第一个线程先运行持有锁2秒
        runTread(() -> {
            //由于没有开启偏向锁，第一个线程得到锁时，是轻量级锁状态
            synchronized (monitor) {
                print(monitor,"线程1-加锁中-轻量级锁(000)");
                sleep(2000L);
            }
        });
        sleep(1000L);
        //另一个线程1秒后开始参与锁争抢
        runTread(() -> {
            //此时共有两个线程争抢同一把锁，轻量级锁会膨胀为重量级锁状态
            synchronized (monitor) {
                print(monitor,"线程2-加锁中-重量级锁(010)");
            }
        });
        //睡一会儿，保证上面的线程运行结束都已经释放锁
        sleep(2000L);
        //此时没有线程对monitor加锁，恢复未锁定状态
        print(monitor,"全部释放锁后-恢复到未锁定状态(001)");
    }


    /**
     * 演示锁变化过程：偏向锁->轻量级锁
     */
    public static void methodB(){
        //JVM启动5秒后才会启用可偏向模式
        Object monitor = newObjectWaitBiasLockModeNo();
        print(monitor,"加锁前-可偏向状态(101)");
        runTread(() -> {
            synchronized (monitor) {
                // 为了演示偏向锁到轻量级锁，
                // 这里需要持有锁的时间很短，否则线程2自旋超时，锁会变为重量级锁，捕捉不到轻量级锁的状态
                // 由于打印对象内存布局比较耗时，所以这里不打印内存布局了，直接显示获取到锁即可
                System.out.println("线程1获取到锁-此时处于偏向锁状态");
            }
        });
        runTread(() -> {
            /**
             * 如果线程1先获取到锁，此时线程2参与锁争抢，
             * 刚开始争抢时，偏向锁变为轻量级锁，线程2开始CAS自旋获取锁，
             * 由于线程1持有锁时间很短，通过自旋很快便获得锁
             * 所以此时当线程2获取到锁时，锁状态为轻量级锁
             * （线程2也可能先获取到锁，所以可以多运行几遍，直到让线程1先获取到锁）
             */
            synchronized (monitor) {
                print(monitor,"线程2-加锁中-轻量级锁(000)");
            }
        });
        //所有线程释放锁后
        sleep(2000L);
        print(monitor,"所有线程释放锁后-未锁定状态(001)");
    }


    /**
     * 演示启用偏向锁模式下：偏向锁->重量级锁
     */
    public static void methodC(){
        //等待5秒钟，JVM启动5秒后才会启用可偏向模式
        Object monitor = newObjectWaitBiasLockModeNo();
        print(monitor,"加锁前-可偏向状态(101)");
        //第一个线程先运行
        runTread(() -> {
            //此时应该是偏向锁状态,持有锁1秒
            synchronized (monitor) {
                print(monitor,"线程1-加锁中-偏向锁(101)");
                sleep(1000L);
            }
        });
        runTread(() -> {
            /**
             * 如果线程1先获取到锁，此时线程2参与锁争抢，
             * 刚开始争抢时，偏向锁变为轻量级锁，线程2开始CAS自旋获取锁，
             * 由于线程1持有锁时间很长，自旋超时后，锁膨胀为重量级锁
             * 所以此时当线程2获取到锁时，锁状态为重量级锁
             * （线程2也可能先获取到锁，所以可以多运行几遍，直到让线程1先获取到锁）
             */
            synchronized (monitor) {
                print(monitor,"线程2-加锁中-重量级锁(010)");
            }
        });
        //1秒后，所有线程释放锁
        sleep(2000L);
        print(monitor,"所有线程释放锁后-未锁定状态(001)");
    }


    /**
     * 演示偏向锁状态下，调用wait()方法，偏向锁会升级为重量级锁
     */
    public static void methodD(){
        //JVM启动5秒后才会启用可偏向模式
        Object monitor = newObjectWaitBiasLockModeNo();
        print(monitor,"加锁前-可偏向状态(101)");
        //第一个线程先运行
        runTread(() -> {
            //此时应该是偏向锁状态,持有锁1秒
            synchronized (monitor) {
                print(monitor, "线程1-加锁中-wait之前-偏向锁(101)");
                try {
                    System.out.println("开始wait\n");
                    monitor.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //等待1秒，使线程1处于wait()状态
        sleep(1000L);
        print(monitor, "线程wait中-重量级锁(010)");

        synchronized (monitor) {
            //唤醒线程1，
            monitor.notify();
        }
        //等待1秒钟，所有线程都释放锁
        sleep(1000L);
        print(monitor, "线程释放锁-未锁定状态(001)");
    }

    /**
     * 演示锁状态变化过程：可偏向->偏向锁状态->轻量级锁状态->重量级锁
     * 以10个线程为例，按照一定间隔时间，依次串行获取同一把锁，
     * 平均在第3个或第4个线程到来时，锁会升级成轻量级锁（统计数据）
     *
     * 线程数: 间隔毫秒数 : 第几个线程变为轻量级锁  : 平均在第几个线程升级为轻量级锁
     * 10   :   1000   :  6-5-2-5-4-3-3-4-2-4  :   3.6
     * 10   :   500    :  4-2-3-3-4-5-6-4-2-4  :   3.7
     * 10   :   200    :  4-5-4-3-4-3-4-2-2-2  :   3.3
     * 10   :   100    :  2-2-2-5-2-3-5-3-6-3  :   3.3
     * 10   :   50     :  5-4-2-2-6-2-2-5-6-4  :   3.8
     *
     * @param threadNumb 线程数
     * @param intervalTime 线程之间间隔毫秒数
     */
    public static void methodE(int threadNumb, Long intervalTime){
        //等待5秒钟，JVM启动5秒后才会启用可偏向模式
        Object monitor = newObjectWaitBiasLockModeNo();
        print(monitor,"对象初始状态-可偏向状态(101)");
        //这个循环中，通过依次串行获得锁，演示锁从偏向锁到轻量级锁的过程
        for (int t = 0; t < threadNumb; t++){
            int i = t+1;
            runTread(() -> {
                synchronized (monitor) {
                    print(monitor,"线程"+ i + "-加锁中");
                }
            });
            //间隔一定的时间
            sleep(intervalTime);
            print(monitor,"间隔"+ intervalTime +"秒-线程" + i + "释放锁后");
        }

        //这里演示从轻量级到重量级的过程
        sleep(intervalTime);
        runTread(() -> {
            //这里获得的锁是轻量级锁.持续持有2秒钟
            synchronized (monitor) {
                print(monitor,"线程-加锁中-轻量级锁(000)");
                sleep(2000L);
            }
        });
        //一秒后
        sleep(1000L);
        //新的线程参与锁争抢
        runTread(() -> {
            //这里获得的锁是重量级锁
            synchronized (monitor) {
                print(monitor,"线程-加锁中-重量级锁(010)");
            }
        });
        //最后状态
        sleep(2000L);
        print(monitor,"所有线程释放锁后-未锁定状态(001)");

        runTread(() -> {
            //这里获得的锁是轻量级锁.持续持有2秒钟
            synchronized (monitor) {
                print(monitor,"线程-加锁中-轻量级锁(000)");
                sleep(2000L);
            }
        });
    }


    /**
     * 开启偏向模式情况下，
     * 在偏向锁状态下，调用hashCode()方法，偏向锁会升级为重量级锁
     */
    public static void methodF(){
        //JVM启动5秒后才会启用可偏向模式
        Object monitor = newObjectWaitBiasLockModeNo();
        print(monitor,"加锁前-可偏向状态(101)");
        //第一个线程先运行
        runTread(() -> {
            //此时应该是偏向锁状态,持有锁1秒
            synchronized (monitor) {
                print(monitor, "线程1-加锁中-hashCode之前-偏向锁(101)");
                monitor.hashCode();
                print(monitor, "线程1-加锁中-hashCode之后-重量级锁(010)");
            }
        });
    }


    /**
     * 开启偏向模式情况下，
     * 匿名偏向对象(101)调用hashCode()方法后，会变为未锁定状态(001)
     * 后续的上锁也不再是偏向锁，而是轻量级锁
     */
    public static void methodG(){
        //JVM启动5秒后才会启用可偏向模式
        Object monitor = newObjectWaitBiasLockModeNo();
        print(monitor,"未加锁-初始状态-可偏向状态(101)");
        monitor.hashCode();
        print(monitor,"未加锁-调用hashCode()-为锁定状态(001)");
        //第一个线程先运行
        runTread(() -> {
            //此时应该是偏向锁状态,持有锁1秒
            synchronized (monitor) {
                print(monitor, "线程1-加锁中-轻量级锁状态(000)");
            }
        });
    }


    /**
     * 创建新线程并执行
     * @param runnable 运行内容
     */
    public static void runTread(Runnable runnable){
        new Thread(runnable).start();
    }

    /**
     * 等待5秒后，创建对象
     * 保证创建的对象是可偏向的
     * @return Object
     */
    public static Object newObjectWaitBiasLockModeNo(){
        System.out.println("**************【JVM已经启动，等待5秒...】*******************");
        sleep(5000L);
        System.out.println("**************【偏向模式已经开启，创建锁对象】*******************");
        return new Object();
    }


    /**
     * 直接创建对象
     * @return Object
     */
    public static Object newObject(){
        System.out.println("**************【JVM刚刚启动，还未开启偏向模式】*******************");
        return new Object();
    }

    /**
     * 打印对象的内存布局细节
     * @param o 需要展示内存布局的对象
     * @param str 消息内容
     */
    public static void print(Object o,String str){
        System.out.println("****************【"+ str +"】****************\n"
            + ClassLayout.parseClass(o.getClass()).toPrintable(o));
    }

    /**
     * 睡眠
     * @param s 毫秒数
     */
    public static void sleep(Long s) {
        if(s <= 0){
            return;
        }
        try {
            Thread.sleep(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    private static int i = 6;

    public static void main(String[] args) throws Exception{
        int i = LockStudy.i;

        if (i == 0) {
            methodA();
        }
        if (i == 1) {
            methodB();
        }
        if (i == 2) {
            methodC();
        }
        if (i == 3) {
            methodD();
        }
        if (i == 4) {
            methodE(10, 50L);
        }
        if (i == 5) {
            methodF();
        }
        if (i == 6) {
            methodG();
        }
    }


}
